#!/usr/bin/python
import rpyc
import datetime
import time
import os
import hashlib
import messages
import cPickle
import sys 
import socket
import fcntl
import struct
import random
from threading import Thread

global myProc
masterAddress = 'localhost:1234'

            
def findNode(self, fileKey):
    if fileKey in self.routingCache:
        print 'Next Node is: ' + self.routingCache[fileKey]
    else:
        print fileKey + ' does not exist in routing cache'

def getFileKey(descriptor):
            temp = hashlib.sha1()
            temp.update(descriptor)
            return temp.hexdigest()

def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15])
    )[20:24])

proc = None
masterProcess = "localhost:6060"
localIP = 'localhost'
localProcess = ''


class MessageHandler(Thread): 
    def __init__(self, msg, destination):
        Thread.__init__(self)
        self.msg = msg
        self.destination = destination
    def run(self):
        conn = getRemoteService(self.destination)
        if self.msg.type == messages.REQ_DATA:
            conn.root.look_up(cPickle.dumps(self.msg))

class Message():
    pass
        
class MyService(rpyc.Service):
    
    test = "this is a test"
    
    def on_connect(self):
        print 'a node connected'
         # code that runs when a connection is created (to init the serivce, if needed
        pass
		  
    def on_disconnect(self):
		 # code that runs when the connection has already closed (to finalize the service, if needed)
        pass
    
    def exposed_change(self):
        myProc.change()
        
    def exposed_myprint(self):
        myProc.myprint()
        
    def exposed_addNode(self, nodeid, address, ttl):
        myProc.addNode(nodeid, address, ttl)
    
    def exposed_status(self):
        myProc.status()
        
    def exposed_searchNode(self, inmsg):
        return myProc.searchNode(inmsg)
        
#    def exposed_insertFile(self, fileName, address, ttl, path):
#        return myProc.insertNewFile(fileName, address, ttl, path)
            
    def exposed_insertFile(self, message):
        return myProc.insertNewFile(message)
            
class FreeNetProc():
    test = "init val"
    routingCache = {}
    ip = "localhost"
    port = 0
    address = ip + ':' + str(port)
    nodeid = getFileKey(address)
    datastore_path = 'datastore/'
    
    def change(self):
        self.test = "kind works"


    
    
    def getClosestNode(self , fileKeyHex, path):
        print 'Looking for closest node to ' + fileKeyHex
        fileKey = hextoint(fileKeyHex)

        from copy import deepcopy
        temp = deepcopy(self.routingCache)
    
            
        while True:
            if len(temp) == 0 :
                print '-10'
                return -10
            keys = temp.keys()
            keys.sort()
    
            
            i=0
            varHex = keys[i]
            var = hextoint(varHex)
            
            if fileKey < var:
                if self.routingCache[varHex] in path:
                    del temp[varHex]
                    continue
                print '1'
                return self.routingCache[varHex]
            
            if fileKey > hextoint(keys[len(keys)-1]):
                if self.routingCache[keys[len(keys)-1]] in path:
                    print 'temp is ',
                    print temp
                    del temp[keys[len(keys)-1]]
                    print 'temp is (after del): ',
                    print temp
                    continue
                print '2'
                return self.routingCache[keys[len(keys)-1]]
            
            i +=1
            
            while i < len(keys) :
                curhex = keys[i]
                cur = hextoint(curhex)
                prevhex = keys[i-1]
                prev = hextoint(prevhex)
                if fileKey <= cur and fileKey >= prev:
                    downdiff = fileKey-prev
                    updiff = cur - fileKey
                    if downdiff < updiff:
                        res = self.routingCache[prevhex]
                        reskey = prevhex
                    else: 
                        res = self.routingCache[curhex]
                        reskey = curhex
                        #if res == self.address:
                if res in path: 
                    del temp[reskey]
                    continue
                else:
                    print '3'
                    return res
                i+=1
    
    
    

    def myprint(self):
        print self.test
        self.test = "new value"
    
    def init (self, port):
        print "Initializing FreeNetProc..."
        self.ip = "localhost"   # fix later?
        self.port = port         # from argument
        self.address = self.ip + ":" + str(self.port)
        self.nodeid = getFileKey(self.address)
        self.routingCache[self.nodeid] = self.address
        self.routingCache[getFileKey(masterAddress)] = masterAddress
        self.datastore_path = 'datastore/' + str(self.port) + '/'
#       temp1 = hashlib.sha1()
#       temp1.update('a')
#       temp2 = temp1.hexdigest()
#       self.routingCache[temp2] = '145.94.186.245:8080'

        print 'Initialized FreeNetProc with id ' + self.nodeid
        
       
        
    
    def startCommandLine(self):
        pid = os.fork()
        if pid:
            self.commandLine()
            print str(pid)
            os.kill(pid,15)
            sys.exit(0)
        else:
            return

    def commandLine(self):
        while True:
                command = raw_input('freenet~> ')
                if command == 'exit':
                    print 'Quiting'
                    return
                if command == 'join':
                    try:
                        conn = getRemoteService(masterAddress)
                        conn.root.addNode(self.nodeid, self.address, 5)
                    except:
                        print "Node: " + masterAddress + " is not accessable"
                if command == 'status':
                    conn = getRemoteService(self.address)
                    conn.root.status()
                elif command.startswith('search '):
                    query = command[7:]
                    print 'Searching for "' + query + '" ...'
                    fileName = query
                    conn = getRemoteService(self.address)
                    
                    msg = Message();
                    
                    msg.path = list()
                    msg.fileName = fileName
                    msg.ttl = 1
                   
                    result = conn.root.searchNode(cPickle.dumps(msg))
                    result = cPickle.loads(result)
                    if result.status == 1:
                        print "file found"
                    elif result.status == 0:
                        print "file not found"
                    elif result.status == 10:
                        print "Node not accessible"
                        continue
                    print result.path
                      
                elif command.startswith('insert '):
                    query = command[7:]
                    
                    if not os.path.exists("datastore/"+query):
                        print "File does not exist."
                        continue
                    
                    fileName = query
                    conn = getRemoteService(self.address)
                    result = cPickle.loads(conn.root.searchNode(fileName, 5))
                    if result.status == 1:
                        print "Name taken, choose another name"
                        continue
                    else:
                        print 'Path clear. Inserting file in the chain...'
                        print result.path
                        file = open("datastore/" + fileName, 'r')
                        message = Message()
                        message.path = result.path
                        message.data = file.read()
                        message.fileName = fileName
                        message.srcaddress = self.address
                        file.close()
                        message.ttl = 1
                        conn = getRemoteService(self.address)
                        conn.root.insertFile(cPickle.dumps(message))
                        

    def status(self):
        print '################### NODE STATUS ######################################################'
            
        print 'NODE ID =      ' + self.nodeid
        print 'PORT    =      ' + str(self.port)
        print 'ADDRESS =      ' + self.address
        print
        
        print '*****************************ROUTING CACHE****************************************'
        print '                  Key                                          VALUE'
        print
                
        for k in self.routingCache.keys():
            print k + '                       ' + self.routingCache[k]
        print
        print '#######################################################################################'

    def addNode(self, nodeid, address, ttl):
        if not self.routingCache.has_key(nodeid):
            print "Adding NodeID " + nodeid + " at address " + address
            self.routingCache[nodeid] = address
        
        if (ttl > 0):
            print "TTYL is " + str(ttl)
            randomAddress = random.choice(self.routingCache.values())
            print "Random Address is: " + randomAddress
            if ((randomAddress != address) & (randomAddress != self.address)):
                print "sent add request to next node"
                conn = getRemoteService(randomAddress)
                conn.root.addNode(nodeid, address, ttl - 1)
            else:
                self.addNode(nodeid, address, ttl-1)
  
    def searchNode(self, inmsg):
        inmsg = cPickle.loads(inmsg)
        
        fileKey = getFileKey(inmsg.fileName)
        inmsg.path.append(self.address)
        
        result = Message()
        
        print "Searching for fileKey: " + fileKey
        print "TTL is " + str(inmsg.ttl)
        if self.routingCache.has_key(fileKey):
            print "This node has file!"
            srcfile = open(self.datastore_path + inmsg.fileName,'r')
            result.fileData = srcfile.read()
            srcfile.close()
            result.status = 1
            result.path = inmsg.path
            return cPickle.dumps(result)
        else:
            print "File not found so far"
            
            if (inmsg.ttl > 0):
                
                print "Path so far "
                print inmsg.path
                nextAddress = self.getClosestNode(getFileKey(inmsg.fileName),inmsg.path)
                print "Next address is: " + str(nextAddress)

                print "Requesting at next node"
                try:
                    conn = getRemoteService(nextAddress)
                    inmsg.ttl = inmsg.ttl -1
                    result = cPickle.loads(conn.root.searchNode(cPickle.dumps(inmsg)))
                except:
                    result.status = 10
                
                if result.status == 1:
                    print "FOUND: CACHE FILE"
                    if not os.path.exists(self.datastore_path):
                        print 'Dir not exist, creating it'
                        os.mkdir(self.datastore_path)                        
                    dstfile = open(self.datastore_path + inmsg.fileName,'w')
                    dstfile.write(result.fileData)
                    dstfile.close()
                elif result.status == 0:
                    print "NOT FOUND"
                elif result.status == 10:
                    return cPickle.dumps(result)
                    
                result.path.append(self.address)
                return cPickle.dumps(result)
            else:
                print "File not found at all"
                result.status = 0
                result.path = path
                return cPickle.dumps(result)




    def insertNewFile(self, message):
        print 'Inserting File'
        message = cPickle.loads(message)
        print message.path
        if not os.path.exists(self.datastore_path):
            print 'Dir not exist, creating it'
            os.mkdir(self.datastore_path)
        file = open(self.datastore_path + message.fileName, 'w')
        file.write(message.data)
        file.close
        self.routingCache[getFileKey(message.fileName)] = message.srcaddress
        
        print 'traversing along the chain ',
        print message.path 
        
        if len(message.path) > 0 :
            nexthop = message.path.pop()
            
            conn = getRemoteService(nexthop)
            conn.root.insertFile( cPickle.dumps(message ))
            
            

  
    

def hextoint(i):
#    intMode = True
#    if intMode: return i
    return int(i, 16)


def getRemoteService(address):
    split = address.split(':')
    ip = split[0]
    port = split[1]
    print "Initiating a connection to " +  ip  + " on port " + port
    port = int(port)
    try:
        connection = rpyc.connect(ip, port)
    except socket.error:
        raise Exception("Connection could not be established")
   #print connection.root.recieveJoinAnnouncement()
    return connection


    

if __name__ =="__main__":

        if len(sys.argv) != 2:
            print "Usage: ./myserver.py port_number"
            sys.exit(1)
        port = sys.argv[1]
        
        
        myProc = FreeNetProc()
        myProc.init(int(port))
        myProc.startCommandLine()
	'''
	myProc.routingCache[5] = "local:1234"
	myProc.routingCache[10] = "local:9090"
	myProc.routingCache[30] = "local:8080"

	path = list()
	path.append('local:9090')
	path.append( 'local:2020')
	'''	
        print "\nStarting Server\n"
        from rpyc.utils.server import ThreadedServer
        t = ThreadedServer( MyService , port=int(port))
        t.start() 
        
